Define the Dart a2ui_agent API and its tests, limited to protocol v0.9 - #2408
Define the Dart a2ui_agent API and its tests, limited to protocol v0.9#2408polina-c wants to merge 31 commits into
Conversation
Implements the API surface described by blueprints/modules/a2ui_agent.blueprint.md for the Dart agent SDK, moves the pieces that belong to a2ui_core into a2ui_core, and moves shared test data into conformance/ so every SDK is measured against one dataset. Most of the agent API throws UnimplementedError; the mechanical parts are implemented. Tests describe the intended behaviour of everything still stubbed and are marked skip: with the reason, so `dart test` doubles as the implementation checklist. a2ui_core - Catalog now takes two type parameters, Catalog<C extends ComponentApi, F extends FunctionApi>, so agents can hold schema-only functions. Breaking. - Adds A2uiProtocolVersion, Catalog.fromJson/catalogSchema/copyWith, A2uiRendererCapabilities, A2uiValidator and the conformance error categories. - Fixes DataModel.set silently dropping a write through a primitive. conformance/ - New core/data_model.yaml, migrated from web_core's data-model.test.ts. - New agent/request_processor.yaml for the blueprint's primary use case. - Basic-catalog cases added to core/catalog.yaml, agent/parser.yaml and agent/inference_format.yaml, referencing the published spec catalog by path. renderers/web_core - data-model.test.ts becomes a harness over the shared dataset; JavaScript specific behaviour stays local and is documented on both sides. CI - Adds a dart_packages job so the Dart packages are formatted, analyzed with --fatal-infos, and tested.
There was a problem hiding this comment.
Code Review
This pull request introduces a shared conformance test suite for the reactive data model and request processor, alongside updates to the Dart a2ui_agent and a2ui_core packages to support protocol version gating (v0.9), schema-only catalogs, and payload validation. It also fixes a bug in DataModel.set to throw an error when writing through a primitive parent path. The review feedback highlights opportunities to improve Dart idiomaticity by leveraging compile-time exhaustiveness checking for sealed classes, enhancing runtime safety during inline catalog parsing, and optimizing repeated map lookups in catalog schema generation.
Formatting: the Dart formatter in the SDK CI runs (3.13.1, via Flutter stable 3.47.1) disagreed with 3.12.2 on eight test files. Reformatted with 3.13.1. Conformance: six cases added to the shared suites asserted behaviour only the Dart SDK has, which broke the Python and Kotlin harnesses that already consume those suites. A shared suite is a contract every implementation satisfies, so these move back to Dart's own tests, where they were already covered: - parse_full rejecting an unsupported or missing protocol version, and an unknown message type. The Python and Kotlin parsers do not validate the version while parsing. - prune with allowed_functions. Function pruning is implemented by the Dart FunctionPruningTransformer only. - select_catalog raising when renderer and agent share no catalog. Kotlin raises with a different message; Python returns no selection. conformance/README.md now separates these "one SDK has it, others do not yet" gaps from genuine language-level exclusions, and lists the three above. The cases that do hold everywhere stay shared: basic catalog loading and selection, component pruning with anyComponent narrowing, and parsing a v0.9 basic catalog payload.
Notifications bypassed the signal's equality check (`force: true`), so every observer on a path related to a write was woken, including observers whose own value was unchanged. Watching /a/b and then writing /a fired the observer even though /a/b was absent before and after. The check was bypassed because a Map or List mutated in place keeps its identity and would compare equal. Hand the signal a copy of a container instead: containers still compare unequal and still notify, while unchanged primitive and absent values no longer do. This is what the web_core renderer already does. Verified: an unchanged descendant no longer fires; a root replacement still wakes the root observer but not an unrelated one; an ancestor of an in-place mutation still fires; rewriting a path with the value it already holds does not. The two implementations now agree, so the behaviour moves into the shared suite: core/data_model.yaml regains the notification assertion on root replacement and gains cases for an unchanged descendant and a same-value rewrite. The corresponding exclusion note and the web_core local test are removed.
…-and-tests' into dart-a2ui-agent-api-and-tests
…-and-tests' into dart-a2ui-agent-api-and-tests
…-and-tests' into dart-a2ui-agent-api-and-tests
nan-yu
left a comment
There was a problem hiding this comment.
Leaving my initial set of comments for now. I'll continue to review the remaining later.
|
|
||
| - name: test_select_basic_catalog_by_id_v0_9 | ||
| description: A renderer that declares the basic catalog id negotiates to it. | ||
| action: select_catalog |
There was a problem hiding this comment.
Is this to test the resolve_catalogs helper,
https://docs.google.com/document/d/17BuULV88IlsUStOFthGMgfeW1XMtLUyQDm3oflgXi-k/edit?pli=1&resourcekey=0-fltDWdFS4VEdDHSTzq--QA&tab=t.0#bookmark=id.im80bpefqd27?
select_catalog is the old legacy API, and is no longer available in the new blueprint.
|
|
||
| - name: test_load_basic_catalog_v0_9 | ||
| description: Loads the published v0.9 basic catalog document from disk. | ||
| action: load_catalog |
There was a problem hiding this comment.
This is actually testing the loading function in the CatalogProvider. We could either make it a unit test, or put it in its own conformance suite, but definitely not belong to the inference_format.yaml.
| # catalog still advertises components the agent may not emit. The ref shapes | ||
| # here match `specification/v0_9_1/catalogs/basic/catalog.json`. | ||
|
|
||
| - name: test_prune_components_narrows_any_component_union |
There was a problem hiding this comment.
The pruning function doesn't belong to a2ui-core, but in the a2ui-agent. Should we put these tests in a separate catalog_transformer.yaml test suite?
| # messages differ between implementations, so `message` is matched as a regular | ||
| # expression against a substring the implementations share. | ||
|
|
||
| - name: test_processor_creates_surface |
There was a problem hiding this comment.
FYI, we added more tests on the v1_0 branch, https://github.com/a2ui-project/a2ui/blob/v1_0/conformance/core/message_processor.yaml.
If this PR is only about a2ui_agent, can we remove a2ui_core related changes?
| /// The capabilities this agent advertises, mirroring | ||
| /// `specification/v0_9_1/json/server_capabilities.json`. | ||
| Map<String, Object?> get agentCapabilities => { | ||
| 'a2uiVersions': [A2uiProtocolVersion.v0_9.jsonValue], |
There was a problem hiding this comment.
Those catalogs may belong to multiple protocol versions.
Comment #4 on PR 2408 asked to remove a2ui_core changes; the suite was dropped because v1_0 already carries 51 cases under the same filename with an incompatible case shape. Restored instead as the two cases v1_0 does not cover -- component and data model isolation between surfaces -- written in the v1_0 vocabulary so the files concatenate on merge. Also fixes two suite headers that cited the wrong blueprint section: catalog providers are 3.F and transformers 3.A, not 3.B and 3.C.
Prerequisite for the Dart a2ui_agent API (#2408). Everything here is a change to a2ui_core, or to a consumer of it, split out so the agent PR reviews as agent work only. Catalog and capabilities - `Catalog<C extends ComponentApi, F extends FunctionApi>` (breaking, 0.1.1 -> 0.2.0). Agents parameterise with `CatalogFunction` (signature only), renderers with `FunctionImplementation`. `SchemaCatalog` aliases the agent shape. - `Catalog.fromJson` / `catalogSchema` / `copyWith`, plus schema-only `CatalogComponent` and `CatalogFunction`, so catalog documents round trip through core and a narrowed catalog renders a narrowed document with `$defs/anyComponent` and `$defs/anyFunction` narrowed to match. - `A2uiRendererCapabilities`, mirroring `client_capabilities.json` and web_core's `A2uiClientCapabilities`. - `A2uiProtocolVersion`, and the `A2uiParseError` / `A2uiCompileError` / `A2uiCatalogError` / `A2uiIntegrityError` / `A2uiRecursionError` categories. - `A2uiValidator`, with the v0.9 version gate implemented and the structural and catalog-schema checks declared but stubbed. One bug fixed - `DataModel.set` silently dropped a write whose parent path resolved to a primitive (`/user/name/first` where `/user/name` is a string). It now throws `A2uiDataError`, matching web_core. The shared dataset surfaced this. Notification on an unchanged value is fixed the same way: the signal is handed a copy of a container rather than bypassing the equality check. Shared conformance data - `core/data_model.yaml` (new, `data_model` action): 37 cases migrated from `renderers/web_core/src/v0_9/state/data-model.test.ts`. - `core/message_processor.yaml` (new, `process_messages` action): the two surface-isolation cases the v1_0 branch's suite of the same name does not cover, written in that branch's case vocabulary so the two files concatenate rather than conflict when it lands. - `conformance_schema.json` gains the two actions and the `DataError` category; the file's existing formatting is preserved. web_core consumes the shared data - `tests/conformance/harness.ts` locates `conformance/` by walking up. - `data-model.conformance.test.ts` and `message-processor.conformance.test.ts` run the two suites. Both are additive: the hand-written `data-model.test.ts` and `message-processor.test.ts` are untouched. Also - `blueprints/modules/a2ui_core.blueprint.md`: package boundary and non-goals, catalog immutability and per-catalog protocol version, the version-keyed capabilities objects and their normative schemas, the complete exception hierarchy plus the rule that parsing wire JSON raises from it, and a conformance section (there was none). - `flutter_packages_test.yml` discovers packages under `dart/` as well as `samples/`. The `dart/` packages were not built, analyzed or tested by any workflow before this. - `dart/a2ui_agent` gets the two changes that exposure requires: its `a2ui_core` constraint follows the major bump, and the unnecessary `library;` directive its stub carried is removed.
# Conflicts: # conformance/conformance_schema.json # dart/a2ui_agent/lib/a2ui_agent.dart # dart/a2ui_agent/pubspec.yaml
A renderer can need a smaller catalog for a given use case and derives one the same way. What is agent-owned is the named transformer rules and the config pipeline that applies them, not narrowing itself -- which is the actual reason their conformance data belongs under agent/.
'a capabilities payload above all' said nothing precise; it is just an example, so name it as one. Also applies prettier, which the previous commit skipped.
Follows 562329b on dart-a2ui-core. A renderer can need a smaller catalog for a given use case and derives one the same way. What the agent SDK owns is the named transformer rules and the CatalogConfig pipeline, which is the actual reason their cases live under agent/.
The per-item prose restated what the a2ui_agent blueprint already documents. A list of what is not in core, with one pointer to where it is specified, carries the same boundary without the duplication.
Contributes to #2356, #2373.
Prerequisite for #2439.
Prompt
Implement API and tests for Dart a2ui_agent:
Notes:
Sources of information:
Prerequisite
PR Overview
Implements the API surface described by
a2ui_agent.blueprint.mdfor the Dart agent SDK, limited to protocol v0.9, together with the tests that describe it.Most of the API throws
UnimplementedError; the mechanical parts are implemented. Tests are written against the intended behaviour and markedskip:with a reason, sodart testdoubles as the implementation checklist for the follow-up PR.Addresses the review feedback on flutter/genui#1020: renderer capabilities, validation and catalog-document handling move to
a2ui_core(now #2439);FunctionApiandFunctionImplementationare separated;BundledCatalogProvideris dropped; unit test data moves to the conformance suite.Version limiting
Every entry point that accepts a versioned payload or a capabilities object rejects anything that is not
v0.9, including payloads that omitversion. That gate is real, not stubbed — it routes throughA2uiProtocolVersion.fromJsonfrom #2439.agentCapabilitiesfollowsserver_capabilities.json: a map keyed by protocol version, with each registered catalog advertised under the version its ownCatalogdeclares rather than under one version assumed for the whole registry.dart/a2ui_agent— the APILayout mirrors §2 Directory & Package Structure of the blueprint.
A2uiGenerator,A2uiRequestProcessoragentCapabilitiesreal;createProcessor,promptSnippet,parseResponse,validateExamplesstubbedCatalogConfig,CatalogProvider,FileSystemCatalogProvider,InMemoryCatalogProviderCatalogTransformer,ComponentPruningTransformer,FunctionPruningTransformerParser,TextPart,RawA2uiPart,A2uiPart,RawResponsePartParser.parseResponsereal; format parsers stubbedInferenceFormat(Factory),DirectJsonFormat,ExpressFormatresolveCatalogsBundledCatalogProvideris deliberately absent — nothing needs to ship with the SDK.Alongside unit coverage of each entry point there are two end-to-end walkthroughs: the blueprint's §5 primary use case (
primary_use_case_test.dart), and its "Code Example" section transcribed as runnable Dart (minimal_snippet.dart) so the published example and the API cannot drift apart.conformance/— shared agent dataTests run against the published basic catalog schema, referenced by relative path rather than copied, so suites cannot drift from the spec. One suite per blueprint module:
agent/request_processor.yamlprocess_requestagent/catalog_resolver.yamlresolve_catalogsCatalogErrors and aValidationErroragent/catalog_provider.yamlload_catalogagent/catalog_transformer.yamlprune$defsunions that reference the pruned entriesresolve_catalogsis a new action:select_catalogis the pre-v1.0 single-catalog helper and is not part of the blueprint. Basic-catalog cases are also added toagent/parser.yaml.conformance_schema.jsongains theprocess_requestandresolve_catalogsactions;agent_sdks/python/.../test_conformance.pyreads the two relocated suites so the Python SDK keeps running those cases.blueprints/modules/a2ui_agent.blueprint.mdThe gaps that produced this round of review findings, written down so the next generated implementation does not repeat them:
$defs/anyComponent/$defs/anyFunctionalongside the entries it drops.agent_capabilities(new) — the blueprint said nothing about what an agent advertises back. Now specified: version-keyed per the normative schema, grouped by each catalog's own version, pristine ids not transformed ones.accepts_inline_catalogsbecomes a constructor parameter; it fedresolve_catalogswith no stated source before.BundledCatalogProviderremoved, with the reason and the errors a provider must raise.resolve_catalogs— five numbered behaviour rules, and a note that it supersedesselect_catalog.CI
flutter_packages_test.ymlnow discovers packages underdart/as well assamples/— in #2439, since thedart/packages were not built, analyzed or tested by any workflow before this and #2439 lands first.Verification
All run locally, on this branch with #2439 merged in:
a2ui_agent: format,dart analyze --fatal-infos,flutter testa2ui_core: format,dart analyze --fatal-infos,flutter testweb_core:yarn test,yarn lintconformance:pytest(suite self-validation)yarn build:allprettier --check,validate_blueprints.pyFollow-ups
Prompt generation, response parsing and streaming, capability negotiation, the EXPRESS grammar, and
A2uiValidator's deep checks. Each has skipped tests describing the target behaviour.